﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Graphics.PackedVector;

namespace ProceduralGolf
{
    class GolfCourseLayoutGenerator
    {
        private readonly float DISTANCE_DRIVER;
        private readonly float DISTANCE_OTHER;

        private readonly float FAIRWAY_MAX_ANGLE;
        private readonly float FAIRWAY_CIRCLE_DISTANCE;
        private readonly float FAIRWAY_CIRCLE_POSITION_DEVIATION;
        private readonly float FAIRWAY_START_RADIUS;
        private readonly float FAIRWAY_END_RADIUS;
        private readonly float FAIRWAY_DIAMETER_DEVIATION;

        private readonly float GREEN_CIRCLE_POSITION_DEVIATION;
        private readonly float GREEN_MIN_RADIUS;
        private readonly float GREEN_MAX_RADIUS;
        

        private readonly int HOLE_CIRCLES;
        private readonly int TEE_CIRCLES;

        private readonly int MIN_HAZARDS;
        private readonly int MAX_HAZARDS;
        private readonly int HAZARD_COMPLEX;

        private Game game;

        private Random rand;

        public GolfCourseLayoutGenerator(Game game)
        {
            this.game = game;
            DISTANCE_DRIVER = Configuration.Config.SettingGroups["Course"].Settings["DistanceDriver"].GetValueAsInt();
            DISTANCE_OTHER = Configuration.Config.SettingGroups["Course"].Settings["DistanceOther"].GetValueAsInt();
            
            FAIRWAY_MAX_ANGLE = MathHelper.Pi / Configuration.Config.SettingGroups["Course"].Settings["FairwayAngleReducer"].GetValueAsFloat();
            FAIRWAY_CIRCLE_DISTANCE = Configuration.Config.SettingGroups["Course"].Settings["FairwayCircleDistance"].GetValueAsInt();
            FAIRWAY_CIRCLE_POSITION_DEVIATION = Configuration.Config.SettingGroups["Course"].Settings["FairwayCirclePositionDeviation"].GetValueAsInt();
            FAIRWAY_START_RADIUS = Configuration.Config.SettingGroups["Course"].Settings["FairwayStartRadius"].GetValueAsInt();
            FAIRWAY_END_RADIUS = Configuration.Config.SettingGroups["Course"].Settings["FairwayEndRadius"].GetValueAsInt();
            FAIRWAY_DIAMETER_DEVIATION = Configuration.Config.SettingGroups["Course"].Settings["FairwayDiameterDeviation"].GetValueAsInt();

            GREEN_CIRCLE_POSITION_DEVIATION = Configuration.Config.SettingGroups["Course"].Settings["GreenCirclePositionDeviation"].GetValueAsInt();
            GREEN_MIN_RADIUS = Configuration.Config.SettingGroups["Course"].Settings["GreenMinRadius"].GetValueAsInt();
            GREEN_MAX_RADIUS = Configuration.Config.SettingGroups["Course"].Settings["GreenMaxRadius"].GetValueAsInt();

            HOLE_CIRCLES = Configuration.Config.SettingGroups["Course"].Settings["HoleCircles"].GetValueAsInt();
            TEE_CIRCLES = Configuration.Config.SettingGroups["Course"].Settings["TeeCircles"].GetValueAsInt();

            MIN_HAZARDS = Configuration.Config.SettingGroups["Course"].Settings["MinHazards"].GetValueAsInt();
            MAX_HAZARDS = Configuration.Config.SettingGroups["Course"].Settings["MaxHazards"].GetValueAsInt();
            HAZARD_COMPLEX = Configuration.Config.SettingGroups["Course"].Settings["HazardComplexity"].GetValueAsInt();
            rand = new Random();
        }

        public GolfCourseLayout generateLayout(float course_length, Vector2 course_size, Vector2 terrain_size)
        {   
            /* recht genereren */
            float border = (course_size.Y - course_length)/2.0f;
            Vector2 tee = new Vector2(course_size.X / 2.0f, course_size.Y - border);
            Vector2 hole = new Vector2(course_size.X / 2.0f, border);

            return generateLayout(tee, hole, course_size, terrain_size);
        }

        public GolfCourseLayout generateLayout(Vector2 tee, Vector2 hole, Vector2 course_size, Vector2 terrain_size)
        {
            GolfCourseLayout course = new GolfCourseLayout(game, tee, hole, course_size, terrain_size, par(Vector2.Distance(tee, hole)));
            //generate points
            List<Vector2> points = genPoints(course.CourseTee, course.CourseHole);
            // add points to course
            foreach (Vector2 point in points) course.addPoint(point);
            // generate fairway
            List<Circle> fairwayCircles = new List<Circle>();
            for (int pix = 0; pix < course.Points.Count - 1; pix++)
            {
                Vector2 p1 = points.ElementAt(pix);
                Vector2 p2 = points.ElementAt(pix+1);
                int step = Convert.ToInt32(Vector2.Distance(p1, p2) / FAIRWAY_CIRCLE_DISTANCE);
                 fairwayCircles.AddRange(generateFairway(p1, p2, step, FAIRWAY_CIRCLE_POSITION_DEVIATION, FAIRWAY_START_RADIUS, FAIRWAY_END_RADIUS, FAIRWAY_DIAMETER_DEVIATION));
            }
            foreach(Circle circle in fairwayCircles) course.addFairway(circle);

            // generate hazards
            List<Circle> hazardCircles = gen_hazards(points.GetRange(1, points.Count-1));
            foreach (Circle circle in hazardCircles) course.addHazard(circle);

            // generate green
            List<Circle> greenCircles = generateGreen(course.CourseHole, HOLE_CIRCLES, GREEN_CIRCLE_POSITION_DEVIATION, GREEN_MIN_RADIUS, GREEN_MAX_RADIUS);
            greenCircles.AddRange(generateGreen(course.CourseTee, TEE_CIRCLES, GREEN_CIRCLE_POSITION_DEVIATION, GREEN_MIN_RADIUS, GREEN_MAX_RADIUS));
            foreach (Circle circle in greenCircles) course.addGreen(circle);

            return course;
        }

        private List<Circle> generateFairway(Vector2 startpos, Vector2 endpos, int n_circles, float deviance, float start_radius, float end_radius, float radius_deviance)
        {
            List<Circle> circles = new List<Circle>();

            int distance_between_circles = (int) (Vector2.Distance(startpos, endpos) / (n_circles - 1));
            Vector2 direction = Vector2.Subtract(endpos, startpos);
            direction = limit(direction, distance_between_circles);
            for(int i = 0; i < n_circles; i++)
            {
                Vector2 pos = Vector2.Add(startpos, Vector2.Multiply(direction, i));
                float radius = MathHelper.Lerp(start_radius, end_radius, (float)i/(float)n_circles) + MathHelper.Lerp(-1*radius_deviance, radius_deviance, (float) rand.NextDouble());
                float dev = MathHelper.Lerp(-1*deviance, deviance, (float) rand.NextDouble());

                Vector2 devdirection = new Vector2(-1*direction.Y, direction.X);
                devdirection.Normalize();
                devdirection = Vector2.Multiply(devdirection, dev);
                circles.Add(new Circle(Vector2.Add(pos, devdirection), radius));
            }
            return circles;
        }

        private List<Circle> generateGreen(Vector2 position, int n_circles, float deviance, float min_radius, float max_radius)
        {
            List<Circle> circles = new List<Circle>();
            for(int i = 0; i < n_circles; i++)
            {
                float angle = MathHelper.Lerp(-MathHelper.Pi, MathHelper.Pi, (float)rand.NextDouble());
                Matrix rotation = Matrix.CreateRotationZ(angle);
                Vector2 devdirection = Vector2.Multiply(Vector2.Transform(new Vector2(1,0), rotation), deviance);
                Vector2 pos = Vector2.Add(position, devdirection);
                float radius = MathHelper.Lerp(min_radius, max_radius, (float) rand.NextDouble());
                circles.Add(new Circle(pos, radius));
            }
            return circles;
        }
        
        private int par(float distance)
        {
            if (distance < 300) return 3;
            if (distance < 450) return 4;
            return 5;
        }

        private List<Vector2> genPoints(Vector2 startpos, Vector2 endpos)
        {
            List<Vector2> points = new List<Vector2>();
            float dist = Vector2.Distance(startpos, endpos);
            int par = this.par(dist);
            int n = par - 3;
            points.Add(startpos);
            if(n > 0)
            {
                Vector2 point = gen_point(startpos, endpos, DISTANCE_DRIVER, FAIRWAY_MAX_ANGLE);
                points.Add(point);
                for(int i = 0; i < (n-1); i++)
                {
                    Vector2 next_point = gen_point(point, endpos, DISTANCE_OTHER, FAIRWAY_MAX_ANGLE);
                    points.Add(next_point);
                    point = next_point;
                }
            }
            points.Add(endpos);
            return points;
            
        }

        private Vector2 gen_point(Vector2 startpos, Vector2 endpos, float max_distance, float max_angle)
        {
            Vector2 vect = Vector2.Subtract(endpos, startpos);
            if(vect.Length() < max_distance) max_distance = vect.Length();
            vect = limit(vect, max_distance);
            float angle = MathHelper.Lerp(-max_angle, max_angle, (float)rand.NextDouble());
            Matrix rotation = Matrix.CreateRotationZ(angle);
            vect = Vector2.Transform(vect, rotation);
            return Vector2.Add(startpos, vect);

        }

        private List<Circle> gen_hazards(List<Vector2> positions)
        {
            List<Circle> circles = new List<Circle>();
            foreach(Vector2 pos in positions)
            {
                int n = MIN_HAZARDS + (int)(rand.NextDouble()*(MAX_HAZARDS - MIN_HAZARDS));
                for(int i = 0; i < n; i++)
                {
                    circles.AddRange(gen_hazard(pos, HAZARD_COMPLEX, FAIRWAY_END_RADIUS*0.5f, FAIRWAY_END_RADIUS, 10, 15, 10));
                }
            }
            return circles;
        }

        private List<Circle> gen_hazard(Vector2 position, int n_circles, float min_distance, float max_distance, float min_radius, float max_radius, float deviation)
        {
            List<Circle> circles = new List<Circle>();
            
            float distance = MathHelper.Lerp(min_distance, max_distance, (float)rand.NextDouble());
            Matrix rotation = Matrix.CreateRotationZ(MathHelper.Lerp(-1*MathHelper.Pi, MathHelper.Pi, (float) rand.NextDouble()));
            Vector2 v = Vector2.Transform(new Vector2(distance, 0), rotation);
            Vector2 hazard_pos = Vector2.Add(position, v);
            for(int i = 0; i < n_circles; i++)
            {
                float d = MathHelper.Lerp(0, deviation, (float) rand.NextDouble());
                Matrix rotation2 = Matrix.CreateRotationZ(MathHelper.Lerp(-1*MathHelper.Pi, MathHelper.Pi, (float) rand.NextDouble()));
                Vector2 offset = Vector2.Transform(new Vector2(d, 0), rotation2);
                Vector2 pos = Vector2.Add(hazard_pos, offset);
                float radius = MathHelper.Lerp(min_radius, max_radius, (float) rand.NextDouble());
                circles.Add(new Circle(pos, radius));
            }
            return circles;
        }


        private Vector2 limit(Vector2 v, float dist)
        {
            Vector2 res = v;
            res.Normalize();
            return res * dist;
        }

    }
}
